#include <stdio.h>

#include "rphoton.hpp"
#include "../q3map/radiosity.h"
#include "../rdistrolib/distributionsession.h"

using namespace TDP::Math;
using namespace TDP::Threading;
using namespace RDistro;
using namespace RDX::ObjectManagement;

static CString chartBasePath(TDP::Memory::DefaultAllocator());
static const LinkedList<rpLight> *chartLights;

MultilevelThreadDispatcher *mlDispatcher;
// Supersample clusters are centered on the pixels they represent
// So, for example, the supersample cluster at 0,0 represents the area from (-0.5,-0.5) - (0.5,0.5)


enum rpChartFlags
{
	rpOccupancyMightBeInSolid_kb				= 1,	// Set when brush checks start, cleared for anything out of brush bounds (aggressive scan)
	rpOccupancyContainsGeometry_kb				= 2,	// Set by polygon additions, cleared if MightBeInSolid fails
	rpOccupancyShadowed_kb						= 4,	// Set for anything occluded by a scan
	rpOccupancyContainsCurrentPoly_kb			= 8,	// Self-explanitory
	rpOccupancyContainsCurrentShadowVolume_kb	= 16,	// Self-explanitory
	rpOccupancyReachableByLight_kb				= 32,	// Set for anything that could be hit by the light
	rpOccupancyContainsChartSpace_kb			= 64,	// Might not contain geometry, but does overlap with geometry when fully expanded to all neighboring texels
	rpOccupancyContainsRadiosityPoint_kb		= 128,	// Can collect radiosity

	rpOccupancyFloodFill_kb						= 1,	// Should be set to a bit that's unused by the time radiosity fill starts
};

struct Bounds2D
{
	unsigned int minX, minY, maxX, maxY;
	bool initialized;

	bool Valid() const
	{
		return initialized;
	}

	Bounds2D()
	{
		initialized = false;
	}

	void Expand(unsigned int x, unsigned int y)
	{
		if(!initialized)
		{
			initialized = true;
			minX = maxX = x;
			minY = maxY = y;
		}
		else
		{
			if(minX > x) minX = x;
			if(minY > y) minY = y;
			if(maxX < x) maxX = x;
			if(maxY < y) maxY = y;
		}
	}

	Bounds2D Intersect(const Bounds2D &rs) const
	{
		Bounds2D result;

		if(!initialized || !rs.initialized)
		{
			result.initialized = false;
			return result;
		}

		result.initialized = true;
		if(minX > rs.minX) result.minX = minX; else result.minX = rs.minX;
		if(minY > rs.minY) result.minY = minY; else result.minY = rs.minY;
		if(maxX < rs.maxX) result.maxX = maxX; else result.maxX = rs.maxX;
		if(maxY < rs.maxY) result.maxY = maxY; else result.maxY = rs.maxY;

		if(minX > maxX || minY > maxY)
			result.initialized = false;	// Fully excluded

		return result;
	}

	Bounds2D Downscale(unsigned int factor) const
	{
		Bounds2D result;

		if(!initialized)
			return result;

		result.initialized = true;
		result.minX = minX / factor;
		result.minY = minY / factor;
		result.maxX = maxX / factor;
		result.maxY = maxY / factor;

		return result;
	}

	bool Contains(unsigned int x, unsigned int y) const
	{
		return initialized && (x >= minX && y >= minY && x <= maxX && y <= maxY);
	}
};


// Constructed charts
struct rpConstructedChart
{
	int superscaling;

	unsigned int width;
	unsigned int height;

	unsigned int superWidth;
	unsigned int superHeight;

	unsigned int minX;
	unsigned int minY;

	Vector<unsigned char> superFlags;
	Vector<unsigned char> flags;

	Vector<float> lightingCoefs;
	Vector<FVec3> locations;
	Vector<FVec3> phongNormals;
	Vector<FVec3> phongTV;
	Vector<FVec3> phongSV;
	Vector<FVec3> surfaceNormals;

	explicit rpConstructedChart(const Allocator &alloc)
		: superFlags(alloc),
		  flags(alloc),
		  lightingCoefs(alloc),
		  locations(alloc),
		  phongNormals(alloc),
		  phongSV(alloc),
		  phongTV(alloc),
		  surfaceNormals(alloc)
	{
	}

	void PaintSuperLine(int y, int startX, int endX, unsigned char flag)
	{
		volatile unsigned char *sf = superFlags;
		if(startX < 0) startX = 0;
		if(endX >= static_cast<int>(superWidth)) endX = static_cast<int>(superWidth-1);

		int sourcePoint = y*superWidth + startX;
		int strideLength = endX - startX;
		for(int p=0;p<strideLength;p++)
			sf[sourcePoint+p] |= flag;
	}

	Bounds2D PaintSuperFlags(int coords[3][2], unsigned char flag)
	{
		Bounds2D bounds;
		int midPoint, topPoint, bottomPoint;

		if(coords[0][1] >= coords[1][1] && coords[1][1] >= coords[2][1])
		{ topPoint = 0; midPoint = 1; bottomPoint = 2; }
		else if(coords[0][1] >= coords[2][1] && coords[2][1] >= coords[1][1])
		{ topPoint = 0; midPoint = 2; bottomPoint = 1; }
		else if(coords[1][1] >= coords[0][1] && coords[0][1] >= coords[2][1])
		{ topPoint = 1; midPoint = 0; bottomPoint = 2; }
		else if(coords[1][1] >= coords[2][1] && coords[2][1] >= coords[0][1])
		{ topPoint = 1; midPoint = 2; bottomPoint = 0; }
		else if(coords[2][1] >= coords[0][1] && coords[0][1] >= coords[1][1])
		{ topPoint = 2; midPoint = 0; bottomPoint = 1; }
		else //if(coords[2][1] >= coords[1][1] && coords[1][1] >= coords[0][1])
		{ topPoint = 2; midPoint = 1; bottomPoint = 0; }

		// Go down the entire triangle
		int startY = coords[bottomPoint][1];
		int endY = coords[topPoint][1];

		if(startY < 0) startY = 0;
		if(endY >= superHeight) endY = superHeight-1;

		for(int y=startY;y<endY;y++)
		{
			int shortBottomPoint, shortTopPoint;
			if(y < coords[midPoint][1])
			{
				shortBottomPoint = bottomPoint;
				shortTopPoint = midPoint;
			}
			else
			{
				shortBottomPoint = midPoint;
				shortTopPoint = topPoint;
			}

			// Find the intersection points
			int longEdgeX = (y - coords[bottomPoint][1]) * (coords[topPoint][0] - coords[bottomPoint][0])
				/ (coords[topPoint][1] - coords[bottomPoint][1]) + coords[bottomPoint][0];
			int shortEdgeX = (y - coords[shortBottomPoint][1]) * (coords[shortTopPoint][0] - coords[shortBottomPoint][0])
				/ (coords[shortTopPoint][1] - coords[shortBottomPoint][1]) + coords[shortBottomPoint][0];

			int startX, endX;
			if(longEdgeX < shortEdgeX)
			{
				startX = longEdgeX;
				endX = shortEdgeX;
			}
			else
			{
				startX = shortEdgeX;
				endX = longEdgeX;
			}

			if(startX != endX)
			{
				bounds.Expand(startX, y);
				bounds.Expand(endX, y);
			}

			// Clip to safe bounds
			PaintSuperLine(y, startX, endX, flag);
		}

		return bounds;
	}
	
	FVec2 SuperChartCoordsToTC(int x, int y)
	{
		Float ssFloat(static_cast<Float>(superscaling));
		return FVec2(static_cast<Float>(x - superscaling/2) / ssFloat, static_cast<Float>(y - superscaling/2) / ssFloat);
	}

	void GetSuperChartCoordinates(FVec2 inputCoords, int *outX, int *outY)
	{
		int x = (int)(floorf(inputCoords[0] * (float)superscaling)) + (superscaling/2);
		int y = (int)(floorf(inputCoords[1] * (float)superscaling)) + (superscaling/2);

		*outX = x;
		*outY = y;
	}

	void GetSuperChartCoordinates(const FTriangle2 &tct, int outCoords[3][2])
	{
		for(unsigned int i=0;i<3;i++)
		{
			FVec2 point = tct.points[i];
			GetSuperChartCoordinates(point, &outCoords[i][0], &outCoords[i][1]);
		}
	}

	Bounds2D PaintSuperFlags(const FTriangle2 &tct, unsigned char flag)
	{
		int coords[3][2];
		GetSuperChartCoordinates(tct, coords);
		return PaintSuperFlags(coords, flag);
	}

	void ClearFlags(unsigned char flag)
	{
		unsigned int totalElements = width*height;
		volatile unsigned char *ptr = flags;
		for(unsigned int i=0;i<totalElements;i++)
			ptr[i] &= (~flag);
	}

	void ClearSuperFlags(unsigned char flag)
	{
		unsigned int totalElements = superWidth*superHeight;
		volatile unsigned char *ptr = superFlags;
		for(unsigned int i=0;i<totalElements;i++)
			ptr[i] &= (~flag);
	}

	void ClearSuperFlags(unsigned char flag, const Bounds2D &bounds)
	{
		if(!bounds.Valid())
			return;

		unsigned int w = superWidth;
		for(unsigned int y=bounds.minY;y<=bounds.maxY;y++)
		{
			volatile unsigned char *ptr = superFlags + y * w;
			for(unsigned int x=bounds.minX;x<=bounds.maxX;x++)
				ptr[x] &= (~flag);
		}
	}

	// Imports radiosity flood-fill coefs
	void ImportCoefs(const CString &path, bool isRRad, unsigned char flag, unsigned char floodFillFlag, float baseScalar, float scalar)
	{
		Vector<float> floodFillBase(TDP::Memory::DefaultAllocator());
		Vector<float> floodFillNext(TDP::Memory::DefaultAllocator());
		Vector<bool> filled(TDP::Memory::DefaultAllocator());
		Vector<bool> willBeFilled(TDP::Memory::DefaultAllocator());

		FILE *chartStream = fopen(path.Chars(), "rb");
		if(chartStream == NULL)
			return;

		unsigned int totalElements = width*height;

		// Read data
		unsigned int numCoefs = RP_CHARTMODE_ELEMENTS[config.chartMode];

		floodFillBase.Resize(NULL, numCoefs * totalElements, 0.0f);
		floodFillNext.Resize(NULL, numCoefs * totalElements, 0.0f);
		filled.Resize(NULL, totalElements, false);
		willBeFilled.Resize(NULL, totalElements, false);

		unsigned int numCoefsIn = 0;
		for(unsigned int i=0;i<totalElements;i++)
		{
			unsigned char checkFlag = flags[i];
			if(checkFlag & flag)
			{
				numCoefsIn++;
				//float *mergePoint = lightingCoefs + i * numCoefs;
				float *baseCoefs = floodFillNext + i * numCoefs;
				fread(baseCoefs, sizeof(float) * numCoefs, 1, chartStream);

				willBeFilled[i] = true;
				//MergeCoefs(coefs, lightingCoefs + i * numCoefs, scalar);
			}
			else
				willBeFilled[i] = false;
		}

		fclose(chartStream);

		float neighboringClusters[RP_CHARTMODE_MAX_ELEMENTS*4];
		float neighborClusterScalars[4];
		bool filledAny = true;
		while(filledAny)
		{
			filledAny = false;

			// Copy the incoming chart into the current one
			memcpy(floodFillBase, floodFillNext, sizeof(float) * numCoefs * totalElements);
			memcpy(filled, willBeFilled, sizeof(bool) * totalElements);
			
			// Try to fill again
			for(unsigned int i=0;i<totalElements;i++)
			{
				unsigned int x = i % width;
				unsigned int y = i / width;

				bool neighborValid[4];
				unsigned int neighborIndex[4];

				if(filled[i] == true) continue;				// Already filled
				unsigned int checkFlag = flags[i];
				if(!(checkFlag & floodFillFlag)) continue;	// Can't fill this texel

				neighborIndex[0] = i - 1;
				neighborIndex[1] = i + 1;
				neighborIndex[2] = i - width;
				neighborIndex[3] = i + width;
				
				neighborValid[0] = ((x > 0) && filled[neighborIndex[0]]);
				neighborValid[1] = ((x < width-1) && filled[neighborIndex[1]]);
				neighborValid[2] = ((y > 0) && filled[neighborIndex[2]]);
				neighborValid[3] = ((y < height-1) && filled[neighborIndex[3]]);

				unsigned int numNeighbors = 0;
				for(int n=0;n<4;n++)
				{
					if(!neighborValid[n]) continue;

					// Accumulate this cluster
					float *neighborCoefs = floodFillBase + neighborIndex[n] * numCoefs;
					memcpy(neighboringClusters + numNeighbors * numCoefs, neighborCoefs, sizeof(float) * numCoefs);
					numNeighbors++;
				}

				if(numNeighbors)
				{
					filledAny = true;
					for(int n=0;n<4;n++)
						neighborClusterScalars[n] = 1.0f / ((float)numNeighbors);
					MergeCoefs(neighboringClusters, floodFillNext + i * numCoefs, numNeighbors, neighborClusterScalars);
					willBeFilled[i] = true;
				}
			}
		}

		// Merge into the base
		{
			float baselineClusters[RP_CHARTMODE_MAX_ELEMENTS*2];
			float baselineScalars[2];

			baselineScalars[0] = baseScalar;
			baselineScalars[1] = scalar;

			for(unsigned int i=0;i<totalElements;i++)
			{
				memcpy(baselineClusters, lightingCoefs + i * numCoefs, sizeof(float) * numCoefs);
				memcpy(baselineClusters + numCoefs, floodFillBase + i * numCoefs, sizeof(float) * numCoefs);
				MergeCoefs(baselineClusters, lightingCoefs + i * numCoefs, 2, baselineScalars);
			}
		}

	}

	void Export(const CString &path, bool raw, unsigned char flag)
	{
		FILE *chartStream = fopen(path.Chars(), "wb");
		unsigned int totalElements = width*height;

		if(!raw)
		{
			rpChartHeader header;
			header.width = width;
			header.height = height;
			header.baseX = minX;
			header.baseY = minY;
			header.chartMode = config.chartMode;
			fwrite(&header, sizeof(header), 1, chartStream);

			// Write occupancy flags
			unsigned char bits = 0;
			unsigned int bitOffs = 0;
			for(unsigned int i=0;i<totalElements;i++)
			{
				if(flag == 0 || (flags[i] & flag))
					bits |= (1 << bitOffs);
				bitOffs++;

				if(bitOffs == 8)
				{
					fwrite(&bits, 1, 1, chartStream);
					bits = 0;
					bitOffs = 0;
				}
			}
			if(bitOffs)
				fwrite(&bits, 1, 1, chartStream);
		}

		
		// Write data
		unsigned int numCoefs = RP_CHARTMODE_ELEMENTS[config.chartMode];
		for(unsigned int i=0;i<totalElements;i++)
			if(flag == 0 || (flags[i] & flag))
			{
				const float *streamOut = lightingCoefs + i * numCoefs;
				fwrite(streamOut, sizeof(float) * numCoefs, 1, chartStream);
			}

		fclose(chartStream);
	}
};


// This SH implementation divides out[6] by sqrt(3) compared to what it should be
// It is multiplied by sqrt(3) in all of the other math surrounding it instead to make
// all of the math use rational numbers
static void LambertDirectionToSHVector(const FVec3 &dir, float *out)
{
	// Constant
	out[0] = 0.25f;

    // Linear
    out[1] = dir[1] * 0.5f;
    out[2] = dir[2] * 0.5f;
    out[3] = dir[0] * 0.5f;

    // Quadratics
	out[4] = ( dir[0]*dir[1] ) * 3.0f*0.3125f;
	out[5] =  ( dir[1]*dir[2] ) * 3.0f*0.3125f;
	out[6] = ( 1.5f*( dir[2]*dir[2] ) - 0.5f ) * 0.3125f;
	out[7] =  ( dir[0]*dir[2] ) * 3.0f*0.3125f;
	out[8] = 0.5f*( dir[0]*dir[0] - dir[1]*dir[1] ) * 3.0f*0.3125f;
}

void rpLight::GenerateClipPlanes(const FTriangle3 &wst, FPlane3 clipPlanes[3]) const
{
	// Generate clip planes, which face inward toward the triangle
	switch(lightType)
	{
	case rpLight::rpLightTypePoint_k:
		for(int i=0;i<3;i++)
			clipPlanes[i] = FTriangle3(origin, wst.points[i], wst.points[(i+1)%3]).Plane();
		break;
	case rpLight::rpLightTypeSky_k:
		for(int i=0;i<3;i++)
			clipPlanes[i] = FTriangle3(wst.points[i] + direction, wst.points[i], wst.points[(i+1)%3]).Plane().Invert();
		break;
	}
}

bool rpLight::FacingPlane(const Math::FPlane3 &p) const
{
	// Generate clip planes
	switch(lightType)
	{
	case rpLight::rpLightTypePoint_k:
		return p.PointSide(origin) == FPlane3::Front;
	case rpLight::rpLightTypeSky_k:
		return (p.normal.DotProduct(direction) < 0.0f);
	}

	return false;
}


// Actual light calculation
void rpLight::Accumulate(const FVec3 &location, const FVec3 &pointNormal, const FVec3 &pointSV, const FVec3 &pointTV, const FVec3 &surfaceNormal, float occupancy, float *coefs, bool *isVisible) const
{
	FVec3 directionNormalized;
	FVec3 photons;
	FVec3 delta;
	Float32 visibilityAttenuation = 1.0f;

	if(occupancy == 0.0f)
		return;

	switch(lightType)
	{
	case rpLight::rpLightTypePoint_k:
		delta = location - this->origin;
		directionNormalized = delta.Normalize2();
		photons = this->emission / delta.LengthSquared();
		break;
	case rpLight::rpLightTypeSky_k:
		photons = this->emission;
		directionNormalized = direction.Normalize2();
		break;
	};

	float shMultipliers[9];

	float pointDot = -pointNormal.DotProduct(directionNormalized);
	float surfaceDot = -surfaceNormal.DotProduct(directionNormalized);

	if(pointDot <= 0.0f || surfaceDot <= 0.0f)
		return;

	// Add to coefs
	switch(config.chartMode)
	{
	case RP_CHARTMODE_COLOR:
		for(int i=0;i<3;i++)
			coefs[i] += occupancy * pointDot * visibilityAttenuation * photons[i];
		break;
	case RP_CHARTMODE_SH:
		{
			LambertDirectionToSHVector(-directionNormalized, shMultipliers);
			for(int i=0;i<9;i++)
				for(int j=0;j<3;j++)
					coefs[i*3+j] += occupancy * visibilityAttenuation * shMultipliers[i] * photons[j];
			for(int i=0;i<3;i++)
				coefs[27+i] += occupancy * visibilityAttenuation * pointDot * photons[i];
		}
		break;
	case RP_CHARTMODE_SUPERDELUXE:
		{
			FVec3 orthoSV = pointSV.FlatAgainst(pointNormal).Normalize2();
			FVec3 orthoTV = orthoSV.Cross(pointNormal).Normalize2();
			if(orthoTV.DotProduct(pointTV) < 0.0f)
				orthoTV = -orthoTV;
			FVec3 basisVectors[3];
			basisVectors[0] = FVec3(RP_BASIS_0(Float32));
			basisVectors[1] = FVec3(RP_BASIS_1(Float32));
			basisVectors[2] = FVec3(RP_BASIS_2(Float32));
			Float32 basisWeights[3];
			for(int i=0;i<3;i++)
			{
				const FVec3 &basis = basisVectors[i];
				const FVec3 basisTransformed = orthoSV * basis[0] + orthoTV * basis[1] + pointNormal * basis[2];
				Float32 weight = -basisTransformed.DotProduct(directionNormalized);
				if(weight < 0.0f)
					weight = 0.0f;
				basisWeights[i] = weight;
			}

			// Superdeluxe has very abrupt falloff due to the basis vectors conserving energy at low angles.
			// Determine how intense this will wind up being against a flat surface
			Float32 integratedWeight = 0.0f;
			for(int i=0;i<3;i++)
				integratedWeight += basisWeights[i] * basisVectors[i][2];

			Float32 photonPower = photons.Length();
			FVec3 localizedDirection(-directionNormalized.DotProduct(orthoSV), -directionNormalized.DotProduct(orthoTV), -directionNormalized.DotProduct(pointNormal));
			for(int i=0;i<3;i++)
			{
				// 0..2 = RGB
				coefs[i] += occupancy * visibilityAttenuation * pointDot * photons[i];

				if(!isPrimary)
				{
					// (3..5),(6..8),(9..11) = Basis values					
					for(int j=0;j<3;j++)
						coefs[3+j*3+i] += photons[i] * basisWeights[j] * occupancy * visibilityAttenuation * (pointDot / integratedWeight);

					// (12..14) = RGB power
					coefs[12+i] += occupancy * photons[i];

					// (15..17) = Direction
					coefs[15+i] += localizedDirection[i] * photonPower * occupancy;
				}
			}

			if(isPrimary)
			{
				// 18 = Primary light power
				if(this->primaryNoVisibilityFalloff)
					coefs[18] += occupancy * primaryFragmentation;
				else
					coefs[18] += occupancy * primaryFragmentation * visibilityAttenuation;
				*isVisible = true;
			}
		}
		break;
	}
}



FTriangle3 WorldSpaceTriangle(const rpPackedVertex *verts, const UInt16 *tri)
{
	FVec3 v[3];

	for(int i=0;i<3;i++)
	{
		// Convert into pixel space
		v[i] = verts[tri[i]].position;
	}

	return FTriangle3(v[0], v[1], v[2]);
}

void ExtractNormals(const rpPackedVertex *verts, const UInt16 *tri, FVec3 normals[3], FVec3 sv[3], FVec3 tv[3])
{
	FVec3 v[3];

	for(int i=0;i<3;i++)
	{
		const rpPackedVertex *vert = verts + tri[i];
		normals[i] = (vert->svec.Cross(vert->tvecN.Reduce<3>()) * vert->tvecN[3]).Normalize2();
		sv[i] = vert->svec;
		tv[i] = vert->tvecN.Reduce<3>();
	}
}

FTriangle2 TexCoordTriangle(const rpPackedVertex *verts, const UInt16 *tri)
{
	FVec2 tcVerts[3];
	float scalar = config.inputSubchartSize;

	for(int i=0;i<3;i++)
	{
		// Convert into pixel space
		const rpPackedVertex *v = verts + tri[i];
		tcVerts[i] = v->lmTexCoords * scalar - FVec2(Float(0.5), Float(0.5));
	}

	return FTriangle2(tcVerts[0], tcVerts[1], tcVerts[2]);
}

struct rpManifoldTriIterator;

class rpManifoldTriIteratorJob : public SimpleJob
{
private:
	volatile AtomicInt _chartID;
	rpManifoldTriIterator *_iterator;

public:
	explicit rpManifoldTriIteratorJob(rpManifoldTriIterator *iterator)
	{
		_iterator = iterator;
		RDX::Threading::NonAtomicWrite(&_chartID, 0);
	}

	void *GetWork();
	void Work(void *ptr);
	void Twiddle();
};

struct rpManifoldTriIterator
{
	virtual void Iterate(const FTriangle2 &tct, const FTriangle3 &wst, const FVec3 *normals, const FVec3 *sv, const FVec3 *tv) = 0;
	virtual bool StartManifold(const rpManifold *manifold) = 0;		// If false, skip the manifold
	virtual void EndManifold(rpManifold *manifold) = 0;				// Action to perform when the manifold finishes

	void IterateAll(rpManifoldList *mfl)
	{
		unsigned int triIndex = 0;
		unsigned int manifoldIndex = 0;
		for(const ListLink<rpManifold *> *manifoldLL=mfl->manifoldList.First();
			manifoldLL;
			manifoldLL=manifoldLL->Next())
		{
			rpManifold *manifold = manifoldLL->Data();

			if(!StartManifold(manifold))
				continue;

			const UInt16 *tris = manifold->triData;
			const rpPackedVertex *verts = manifold->vertData;

			for(unsigned int i=0;i<manifold->seg->numTris;i++)
			{
				FTriangle2 tct = TexCoordTriangle(verts, tris + i*3);
				FTriangle3 wst = WorldSpaceTriangle(verts, tris + i*3);
				FVec3 normals[3], sv[3], tv[3];
				ExtractNormals(verts, tris + i*3, normals, sv, tv);

				triIndex++;
				Iterate(tct, wst, normals, sv, tv);
			}

			EndManifold(manifold);
		}
	}
	
	void IterateAll(int chartID)
	{
		IterateAll(chartManifolds + chartID);
	}

	// Executes for all charts and runs each segment job in its own thread
	void IterateAllAsynchronous(rpManifoldTriIteratorJob *job)
	{
		mlDispatcher->InstallJob(job, false);
	}

};

void *rpManifoldTriIteratorJob::GetWork()
{
	NonAtomicInt chartID = AtomicIncrement(&_chartID);
	if(chartID >= geometry->numCharts)
		return NULL;
	chartID--;
	rpManifoldList *mfl = chartManifolds + chartID;
	return mfl;
}

void rpManifoldTriIteratorJob::Work(void *ptr)
{
	_iterator->IterateAll(reinterpret_cast<rpManifoldList *>(ptr));
}

void rpManifoldTriIteratorJob::Twiddle()
{
}



struct rpManifoldBoundsCalculator : rpManifoldTriIterator
{
	bool boundsSet;
	int chartMins[2];
	int chartMaxs[2];

	rpManifoldBoundsCalculator()
	{
		boundsSet = false;
		chartMins[0] = chartMins[1] = chartMaxs[0] = chartMaxs[1] = 0;
	}

	bool StartManifold(const rpManifold *manifold)
	{
		return true;
	}

	void EndManifold(rpManifold *manifold)
	{
	}

	void Iterate(const FTriangle2 &tct, const FTriangle3 &wst, const FVec3 *normals, const FVec3 *sv, const FVec3 *tv)
	{
		float worldSpaceArea = wst.Area();

		FPlane3 validClipPlanes[3];

		FVec2 mins2, maxs2;
		mins2 = maxs2 = tct.points[0];
		for(int j=0;j<3;j++)
		{
			validClipPlanes[j] = tct.ClipPlane(j);
			if(j)
			{
				mins2 = mins2.Minimum(tct.points[j]);
				maxs2 = maxs2.Maximum(tct.points[j]);
			}
		}

		// Find the chart bounds of this tri
		// Shrink slightly to discourage rounding error
		int minBounds[2], maxBounds[2];
		minBounds[0] = (int)floorf(mins2[0] + Math::Epsilon_k);
		maxBounds[0] = (int)ceilf(maxs2[0] - Math::Epsilon_k);
		minBounds[1] = (int)floorf(mins2[1] + Math::Epsilon_k);
		maxBounds[1] = (int)ceilf(maxs2[1] - Math::Epsilon_k);

		unsigned int triBounds[2];
		triBounds[0] = (unsigned int)(maxBounds[0] - minBounds[0] + 1);
		triBounds[1] = (unsigned int)(maxBounds[1] - minBounds[1] + 1);

		// Add this tri to the chart bounds
		for(int i=0;i<2;i++)
		{
			if(!boundsSet || minBounds[i] < chartMins[i])
				chartMins[i] = minBounds[i];
			if(!boundsSet || maxBounds[i] > chartMaxs[i])
				chartMaxs[i] = maxBounds[i];
		}

		boundsSet = true;
	}
};

struct rpManifoldRadiosityTestIterator : rpManifoldTriIterator
{
	rpConstructedChart *_chart;
	const FTriangle3 *_receiverWST;

	bool _radiosityExclusionInitialized;
	FPlane3 _rxCutoffPlanes[5];	// Bounds

	void Initialize()
	{
		if(_radiosityExclusionInitialized == false)
		{
			_radiosityExclusionInitialized = true;
			FPlane3 receiverPlane = _receiverWST->Plane();

			float pushoffDistance = config.radiosityPushoffDistance;
			float exclusionDistance = config.radiosityExclusionRadius;

			// Anything completely in front of any of these can't interfere
			_rxCutoffPlanes[0] = receiverPlane.Push(pushoffDistance+exclusionDistance);
			_rxCutoffPlanes[1] = receiverPlane.Push(pushoffDistance-exclusionDistance).Invert();
			for(int i=0;i<3;i++)
				_rxCutoffPlanes[i+2] = _receiverWST->ClipPlane(i).Invert().Push(exclusionDistance);
		}
	}

	bool StartManifold(const rpManifold *manifold)
	{
		return true;
	}

	void EndManifold(rpManifold *manifold)
	{
	}

	void Iterate(const FTriangle2 &tct, const FTriangle3 &wst, const FVec3 *normals, const FVec3 *sv, const FVec3 *tv)
	{
		FPlane3 casterPlane = wst.Plane();
		FPlane3 receiverPlane = _receiverWST->Plane();

		float pushoffDistance = config.radiosityPushoffDistance;
		float exclusionDistance = config.radiosityExclusionRadius;

		// Try to completely cull away this triangle
		bool mightInterfere = true;
		for(int cp=0;cp<5;cp++)
		{
			int sides[3];
			for(int i=0;i<3;i++)
				sides[i] = _rxCutoffPlanes[cp].PointSide(wst.points[i]);
			if(sides[0] == FPlane3::Front && sides[1] == FPlane3::Front && sides[2] == FPlane3::Front)
			{
				mightInterfere = false;
				break;
			}
		}

		if(mightInterfere)
		{
			// Create an expanded version of this triangle
			FCylinder boundCylinders[3];
			FSphere3 boundSpheres[3];
			FPlane3 boundPlanes[5];	// Contained if it's inside all of these
			boundPlanes[0] = wst.Plane().Push(exclusionDistance);
			boundPlanes[1] = wst.Plane().Invert().Push(exclusionDistance);

			for(int i=0;i<3;i++)
			{
				boundCylinders[i] = FCylinder(wst.Edge(i), exclusionDistance);
				boundSpheres[i] = FSphere3(wst.points[i], exclusionDistance);
				boundPlanes[i+2] = wst.ClipPlane(i).Invert().Push(exclusionDistance);
			}

			unsigned int totalElements = _chart->width*_chart->height;
			volatile unsigned char *flags = _chart->flags;
			for(unsigned int i=0;i<totalElements;i++)
			{
				if( (flags[i] & (rpOccupancyContainsCurrentPoly_kb|rpOccupancyContainsRadiosityPoint_kb)) ==
					(rpOccupancyContainsCurrentPoly_kb|rpOccupancyContainsRadiosityPoint_kb) )
				{
					FVec3 pushedLocation = _chart->locations[i] + _chart->surfaceNormals[i] * pushoffDistance;

					// Check planes first
					bool isInside = true;
					for(int j=0;j<5;j++)
						if(boundPlanes[j].PointSide(pushedLocation) == FPlane3::Front)
						{
							isInside = false;
							break;
						}

						// Failing that, try cylinders/spheres
						if(!isInside)
						{
							for(int j=0;j<3;j++)
							{
								if(boundCylinders[j].ContainsPoint(pushedLocation) || boundSpheres[j].ContainsPoint(pushedLocation))
								{
									isInside = true;
									break;
								}
							}
						}

						// Interferes, tick this one off
						if(isInside)
							flags[i] &= ~rpOccupancyContainsRadiosityPoint_kb;
				}
			}
		}
	}
};

struct rpManifoldShadowingIterator : rpManifoldTriIterator
{
	rpConstructedChart *_chart;
	const FTriangle3 *_receiverWST;
	const FTriangle2 *_receiverTCT;
	const rpLight *_light;

	bool StartManifold(const rpManifold *manifold)
	{
		return true;
	}
	
	void EndManifold(rpManifold *manifold)
	{
	}

	void Iterate(const FTriangle2 &tct, const FTriangle3 &wst, const FVec3 *normals, const FVec3 *sv, const FVec3 *tv)
	{
		FPlane3 casterPlane = wst.Plane();
		FPlane3 receiverPlane = _receiverWST->Plane();

		if(casterPlane.PointSide(_receiverWST->points[0]) != FPlane3::Front && 
			casterPlane.PointSide(_receiverWST->points[1]) != FPlane3::Front && 
			casterPlane.PointSide(_receiverWST->points[2]) != FPlane3::Front)
			return;		// Caster doesn't face the receiver
		if(receiverPlane.PointSide(wst.points[0]) != FPlane3::Front && 
			receiverPlane.PointSide(wst.points[1]) != FPlane3::Front && 
			receiverPlane.PointSide(wst.points[2]) != FPlane3::Front)
			return;		// Receiver isn't in front of the caster

		// Needs to be facing the receiver plane and facing away from the caster plane (shadows are cast from the backside)
		if(!_light->FacingPlane(receiverPlane) || _light->FacingPlane(casterPlane))
			return;

		FPlane3 clipPlanes[5];

		_light->GenerateClipPlanes(wst, clipPlanes);

		// Try to cull the receiver away
		for(int i=0;i<3;i++)
		{
			bool behindPlane = true;
			for(int j=0;j<3;j++)
			{
				if(clipPlanes[i].PointSide(_receiverWST->points[j]) != FPlane3::Back)
				{
					behindPlane = false;
					break;
				}
			}

			if(behindPlane)
				return;
		}

		clipPlanes[3] = casterPlane;

		int numClipPlanes = 4;

		FTriangulator rcvTTriang(_receiverTCT->Expand<3>());
		FTriangulator rcvWTriang(*_receiverWST);

		// Can't simply project because point sources can cause points to cast above the horizon
		for(unsigned int y=0;y<_chart->superHeight;y++)
		{
			FVec2 scanStartTC = _chart->SuperChartCoordsToTC(0, y);
			FVec2 scanEndTC = _chart->SuperChartCoordsToTC(_chart->superWidth, y);

			FVec3 startPos = rcvWTriang.PointAtTriangulation(rcvTTriang.TriangulationForPoint(scanStartTC.Expand<3>()));
			FVec3 endPos = rcvWTriang.PointAtTriangulation(rcvTTriang.TriangulationForPoint(scanEndTC.Expand<3>()));

			float scanStartFrac = 0.0f;
			float scanEndFrac = 1.0f;

			bool scanLineValid = true;
			for(int i=0;i<numClipPlanes;i++)
			{
				float startDist = clipPlanes[i].normal.DotProduct(startPos) - clipPlanes[i].dist;
				float endDist = clipPlanes[i].normal.DotProduct(endPos) - clipPlanes[i].dist;

				if(startDist <= 0.0f && endDist <= 0.0f)
				{
					scanLineValid = false;
					break;	// Scanline was completely occluded
				}

				if(startDist >= 0.0f && endDist >= 0.0f)
					continue;	// Scanline is completely in bounds

				// Scanline intersects the plane
				float intersectFrac = startDist / (startDist - endDist);
				float midpointFrac = scanStartFrac + (scanEndFrac - scanStartFrac) * intersectFrac;
				FVec3 midpointPos = startPos + (endPos - startPos) * intersectFrac;

				if(startDist < 0.0f)
				{
					startPos = midpointPos;
					scanStartFrac = midpointFrac;
				}
				else
				{
					endPos = midpointPos;
					scanEndFrac = midpointFrac;
				}
			}

			if(!scanLineValid)
				continue;

			// TODO: Alpha testing
			_chart->PaintSuperLine(y, (int)floorf(scanStartFrac * (float)_chart->superWidth), (int)floorf(scanEndFrac * (float)_chart->superWidth), rpOccupancyShadowed_kb);
		}

	}
};


// Iterates over a manifold and casts all triangles on to it
// This is NOT done asynchronously
struct rpManifoldLightIterator : rpManifoldTriIterator
{
	rpConstructedChart *chart;
	Int32 primaryLightIndex;
	bool conflictingPrimaryLights;

	rpManifoldLightIterator()
	{
		primaryLightIndex = -1;
		conflictingPrimaryLights = false;
	}

	bool StartManifold(const rpManifold *manifold)
	{
		return true;
	}

	void EndManifold(rpManifold *manifold)
	{
		manifold->primaryLightIndex = this->primaryLightIndex;
		manifold->conflictingPrimaryLights = this->conflictingPrimaryLights;
		this->primaryLightIndex = -1;
		this->conflictingPrimaryLights = false;
	}

	void Iterate(const FTriangle2 &globalTCT, const FTriangle3 &wst, const FVec3 *normals, const FVec3 *sv, const FVec3 *tv)
	{
		unsigned int totalSuperElements = chart->superWidth * chart->superHeight;
		unsigned int totalElements = chart->width * chart->height;
		FVec2 tctOffset(chart->minX, chart->minY);

		FTriangle2 tct(globalTCT.points[0] - tctOffset, globalTCT.points[1] - tctOffset, globalTCT.points[2] - tctOffset);

		Bounds2D currentPolyBounds = chart->PaintSuperFlags(tct, rpOccupancyContainsCurrentPoly_kb | rpOccupancyContainsGeometry_kb);

		// TODO: Exclude by brush contents

		FTriangulator tctTriangulator(tct.Expand<3>());
		FTriangulator wstTriangulator(wst);
		FTriangulator phongTriangulator(FTriangle3(normals[0], normals[1], normals[2]));
		FTriangulator svTriangulator(FTriangle3(sv[0], sv[1], sv[2]));
		FTriangulator tvTriangulator(FTriangle3(tv[0], tv[1], tv[2]));

		FVec3 triNormal = wst.Plane().normal;

		unsigned int ss = chart->superscaling;

		chart->ClearFlags(rpOccupancyContainsCurrentPoly_kb);

		// Paint constant values
		Bounds2D currentReduced = currentPolyBounds.Downscale(ss);

		if(currentReduced.Valid())
		{
			for(unsigned int y=currentReduced.minY;y<=currentReduced.maxY;y++)
			{
				for(unsigned int x=currentReduced.minX;x<=currentReduced.maxX;x++)
				{
					unsigned int element = y * chart->width + x;

					for(unsigned int j=0;j<ss*ss;j++)
					{
						unsigned int superX = j % ss + x*ss;
						unsigned int superY = j / ss + y*ss;
						unsigned int superElement = superY*chart->superWidth + superX;
				
						if(chart->superFlags[superElement] & rpOccupancyContainsCurrentPoly_kb)
						{
							chart->flags[element] |= (rpOccupancyContainsCurrentPoly_kb | rpOccupancyContainsGeometry_kb);
							if(x % config.radiosityGranularity == 0 && y % config.radiosityGranularity == 0)
								chart->flags[element] |= rpOccupancyContainsRadiosityPoint_kb;

							FVec3 triangulation = tctTriangulator.TriangulationForPoint(FVec3(x, y, 0));
							chart->locations[element] = wstTriangulator.PointAtTriangulation(triangulation);
							//terVec3 triangulatedLocation = wstTriangulator.PointAtTriangulation(triangulation);
							chart->phongNormals[element] = phongTriangulator.PointAtTriangulation(triangulation).Normalize2();
							chart->phongSV[element] = svTriangulator.PointAtTriangulation(triangulation).Normalize2();
							chart->phongTV[element] = tvTriangulator.PointAtTriangulation(triangulation).Normalize2();
							chart->surfaceNormals[element] = triNormal;

							break;
						}
					}
				}
			}
		}

		// Determine radiosity exclusions
		if(config.radiosityExportPoints || config.radiosityAccumulate)
		{
			rpManifoldRadiosityTestIterator rti;
			rti._chart = chart;
			rti._receiverWST = &wst;
			rti._radiosityExclusionInitialized = false;
			rti.Initialize();
			
			rpManifoldTriIteratorJob asyncJob(&rti);
			mlDispatcher->InstallJob(&asyncJob, false);
		}

		// Accumulate all lights
		if(config.castLights)
		{
			for(const ListLink<rpLight> *lightLink=chartLights->First();lightLink;lightLink=lightLink->Next())
			{
				chart->ClearSuperFlags(rpOccupancyShadowed_kb | rpOccupancyReachableByLight_kb, currentPolyBounds);
				const rpLight *light = lightLink->DataPtr();

				// TODO: Apply this differently if the emitter is a poly
				Bounds2D reachableBounds = chart->PaintSuperFlags(tct, rpOccupancyReachableByLight_kb);

				// Apply shadows from everything
				// TODO: Limit to visible stuff
				rpManifoldShadowingIterator shadowing;
				shadowing._chart = chart;
				shadowing._light = light;
				shadowing._receiverTCT = &tct;
				shadowing._receiverWST = &wst;
				
				{
					rpManifoldTriIteratorJob asyncJob(&shadowing);
					mlDispatcher->InstallJob(&asyncJob, false);
				}

				Bounds2D intersectedBounds = reachableBounds.Intersect(currentPolyBounds);
				Bounds2D reducedIntersected = intersectedBounds.Downscale(ss);
				bool isVisible = false;

				if(reducedIntersected.Valid())
				{
					for(unsigned int y=reducedIntersected.minY;y<=reducedIntersected.maxY;y++)
					{
						for(unsigned int x=reducedIntersected.minX;x<=reducedIntersected.maxX;x++)
						{
							unsigned int element = y * chart->width + x;

							unsigned int occupancy = 0;

							for(unsigned int j=0;j<ss*ss;j++)
							{
								unsigned int superX = j % ss + x*ss;
								unsigned int superY = j / ss + y*ss;
								unsigned int superElement = superY*chart->superWidth + superX;
				
								if( (chart->superFlags[superElement] & (rpOccupancyContainsCurrentPoly_kb | rpOccupancyShadowed_kb | rpOccupancyReachableByLight_kb))
									== (rpOccupancyContainsCurrentPoly_kb | rpOccupancyReachableByLight_kb) )
								{
									// Contains poly, not shadowed
									occupancy++;
								}
							}

							// This might contain the current light
							if(occupancy)
								light->Accumulate(chart->locations[element], chart->phongNormals[element], chart->phongSV[element], chart->phongTV[element], chart->surfaceNormals[element],
									(float)occupancy / (float)(ss*ss), chart->lightingCoefs + element*RP_CHARTMODE_ELEMENTS[config.chartMode], &isVisible);
						}
					}
				}

				if(isVisible)
				{
					if(this->primaryLightIndex != -1)
					{
						if(this->primaryLightIndex != light->lightIndex)
							this->conflictingPrimaryLights = true;
					}
					else
						this->primaryLightIndex = light->lightIndex;
				}
			}
		}

		// Unflag the current polygon
		chart->ClearFlags(rpOccupancyContainsCurrentPoly_kb);
		chart->ClearSuperFlags(rpOccupancyContainsCurrentPoly_kb);
	}
};


// Merges a series of coefficients, with scalars, into a single value
// Usually just a mul-accumulate over the array
void MergeCoefs(const float *src, float *dest, unsigned int numClusters, const float *scalars)
{
	switch(config.chartMode)
	{
	case RP_CHARTMODE_COLOR:
	case RP_CHARTMODE_SH:
	case RP_CHARTMODE_SUPERDELUXE:
		{
			int numElements = RP_CHARTMODE_ELEMENTS[config.chartMode];
			for(int i=0;i<numElements;i++)
				dest[i] = 0.0f;
			for(int i=0;i<numClusters*numElements;i++)
				dest[i%numElements] += src[i] * scalars[i/numElements];
		}
		break;
	}
}

// Amplifies coefficients by a scalar
void AmplifyCoefs(float *coefs, float scale)
{

	switch(config.chartMode)
	{
	case RP_CHARTMODE_COLOR:
	case RP_CHARTMODE_SH:
	case RP_CHARTMODE_SUPERDELUXE:
		for(int i=0;i<RP_CHARTMODE_ELEMENTS[config.chartMode];i++)
			coefs[i] *= scale;
		break;
	}
}

static unsigned int totalRadiosityPoints = 0;

void ExportPrimaryLights(const CString &path, const LinkedList<rpManifold *> *manifoldList)
{
	FILE *plStream = fopen(path.Chars(), "wb");
	for(const ListLink<rpManifold *> *mfl = manifoldList->First();mfl != NULL;mfl = mfl->Next())
	{
		const rpManifold *manifold = mfl->Data();
		fwrite(&manifold->primaryLightIndex, sizeof(manifold->primaryLightIndex), 1, plStream);
	}
	fclose(plStream);
}

void ImportPrimaryLights(const CString &path, const LinkedList<rpManifold *> *manifoldList)
{
	FILE *plStream = fopen(path.Chars(), "rb");
	if(!plStream)
		return;

	for(const ListLink<rpManifold *> *mfl = manifoldList->First();mfl != NULL;mfl = mfl->Next())
	{
		rpManifold *manifold = mfl->Data();
		fread(&manifold->primaryLightIndex, sizeof(manifold->primaryLightIndex), 1, plStream);
	}
	fclose(plStream);
}

// TODO: This is probably better done in two phases
void CollectChart(int chartID)
{
	rpManifoldBoundsCalculator boundsCalc;
	unsigned int coefWidth = RP_CHARTMODE_ELEMENTS[config.chartMode];

	boundsCalc.IterateAll(chartID);

	if(!boundsCalc.boundsSet)
		return;		// Bad chart!

	rpConstructedChart chart(TDP::Memory::DefaultAllocator());

	for(int i=0;i<2;i++)
		if(boundsCalc.chartMins[i] < 0) boundsCalc.chartMins[i] = 0;

	chart.width = boundsCalc.chartMaxs[0] - boundsCalc.chartMins[0] + 1;
	chart.height = boundsCalc.chartMaxs[1] - boundsCalc.chartMins[1] + 1;
	chart.minX = boundsCalc.chartMins[0];
	chart.minY = boundsCalc.chartMins[1];
	chart.superscaling = config.chartSupersampling;

	unsigned int totalElements = chart.width*chart.height;

	chart.superFlags.Resize(NULL, totalElements*chart.superscaling*chart.superscaling, 0);
	chart.flags.Resize(NULL, totalElements, 0);

	chart.lightingCoefs.Resize(NULL, coefWidth*totalElements, 0.0f);
	chart.locations.Resize(NULL, totalElements);
	chart.phongNormals.Resize(NULL, totalElements);
	chart.phongSV.Resize(NULL, totalElements);
	chart.phongTV.Resize(NULL, totalElements);
	chart.surfaceNormals.Resize(NULL, totalElements);

	chart.superWidth = chart.width * chart.superscaling;
	chart.superHeight = chart.height * chart.superscaling;

	rpManifoldLightIterator lighter;

	lighter.chart = &chart;
	lighter.IterateAll(chartID);

	// Calculate chart space occupancy.  This uses a double-size filter.
	unsigned int ss = chart.superscaling;
	for(unsigned int i=0;i<totalElements;i++)
	{
		unsigned int x = i % chart.width;
		unsigned int y = i / chart.width;

		unsigned int occupancy = 0;

		for(unsigned int j=0;j<ss*ss;j++)
		{
			unsigned int superX = j % ss + x*ss;
			unsigned int superY = j / ss + y*ss;

			unsigned int superElement = superY*chart.superWidth + superX;

			if( chart.superFlags[superElement] & rpOccupancyContainsGeometry_kb )
			{
				occupancy++;

				// Flag occupancy using an expanded filter radius
				int baseX = (int)(superX - ss/2) / (int)ss;
				int baseY = (int)(superY - ss/2) / (int)ss;
				unsigned int baseElement = (unsigned int)(baseY*chart.width + baseX);

				if(baseX >= 0)
				{
					if(baseY >= 0)
						chart.flags[baseElement] |= rpOccupancyContainsChartSpace_kb;
					if(baseY < chart.height-1)
						chart.flags[baseElement+chart.width] |= rpOccupancyContainsChartSpace_kb;
				}
				if(baseX < chart.width-1)
				{
					if(baseY >= 0)
						chart.flags[baseElement+1] |= rpOccupancyContainsChartSpace_kb;
					if(baseY < chart.height-1)
						chart.flags[baseElement+1+chart.width] |= rpOccupancyContainsChartSpace_kb;
				}
			}
		}

		if(occupancy > 0 && occupancy < chart.superscaling*chart.superscaling)
			AmplifyCoefs(chart.lightingCoefs + (i * coefWidth), (float)(chart.superscaling*chart.superscaling) / (float)occupancy);
	}

	// Create radiosity point file
	if(config.radiosityExportPoints)
	{
		char path[4096];
		sprintf(path, "%s_photons/chart%i.radpoints", chartBasePath.Chars(), chartID);
		FILE *chartRadPointStream = fopen(path, "wb");

		// Write radiosity points
		unsigned int totalPoints = 0;
		for(unsigned int i=0;i<totalElements;i++)
		{
			rradPoint_t rpoint;
			
			if(chart.flags[i] & rpOccupancyContainsRadiosityPoint_kb)
			{
				rpoint.flat = 0;
				rpoint.chartMode = static_cast<unsigned char>(config.chartMode);

				FVec3 normal = chart.surfaceNormals[i];
				FVec3 location = chart.locations[i] + normal * config.radiosityPushoffDistance;
				for(int j=0;j<3;j++)
				{
					rpoint.normal[j] = normal[j];
					rpoint.point[j] = location[j];
				}
				fwrite(&rpoint, sizeof(rpoint), 1, chartRadPointStream);
				totalPoints++;
				totalRadiosityPoints++;
			}
		}

		fclose(chartRadPointStream);

		totalPoints = 0;
	}

	// Create chart
	CString chartNumber(chartBasePath.Allocator());
	chartNumber.SetToInt(NULL, chartID);

	CString chartNumberedPath(chartBasePath.Allocator());

	chartNumberedPath.Set(NULL, chartBasePath);
	chartNumberedPath.Append(NULL, "_photons/chart");
	chartNumberedPath.Append(NULL, chartNumber);

	CString directLightPath(chartBasePath.Allocator());
	directLightPath.SetConcat(NULL, chartNumberedPath, ".directlight");

	CString chartDataPath(chartBasePath.Allocator());
	chartDataPath.SetConcat(NULL, chartNumberedPath, ".chartdata");

	CString radSamplesPath(chartBasePath.Allocator());
	radSamplesPath.SetConcat(NULL, chartNumberedPath, ".radsamples");

	CString primaryLightPath(chartBasePath.Allocator());
	primaryLightPath.SetConcat(NULL, chartNumberedPath, ".primarylights");

	if(config.castLights)
	{
		chart.Export(directLightPath, true, rpOccupancyContainsGeometry_kb);
		ExportPrimaryLights(primaryLightPath, &chartManifolds[chartID].manifoldList);
	}
	if(!config.noDirect)
	{
		chart.ImportCoefs(directLightPath, false, rpOccupancyContainsGeometry_kb, rpOccupancyContainsChartSpace_kb, 0.0f, 1.0f);
		ImportPrimaryLights(primaryLightPath, &chartManifolds[chartID].manifoldList);
	}

	if(config.radiosityAccumulate)
		chart.ImportCoefs(radSamplesPath, true, rpOccupancyContainsRadiosityPoint_kb, rpOccupancyContainsChartSpace_kb, 1.0f, 0.03125f);

	chart.Export(chartDataPath, false, rpOccupancyContainsChartSpace_kb);
}

class rpCollectChartJob2 : public SimpleJob
{
private:
	unsigned int _numCharts;
	unsigned int _firstChart;
	unsigned int _reportedProgress;
	volatile AtomicInt _chartsFinished;
	DistributionSession *_session;
	volatile AtomicInt _currentChart;
	LightMutex _mtx;
	bool _quiet;

public:
	rpCollectChartJob2(unsigned int firstChart, unsigned int numCharts, DistributionSession *session, bool quiet)
	{
		_numCharts = numCharts;
		_firstChart = firstChart;
		_session = session;
		RDX::Threading::NonAtomicWrite(&_currentChart, 0);
		_chartsFinished.value = 0;
		_quiet = quiet;
		_reportedProgress = 0;
	}

	void ReportProgress()
	{
		if(_quiet)
			return;

		AtomicIncrement(&_chartsFinished);
	}

	void *GetWork()
	{
		NonAtomicInt chart = AtomicIncrement(&_currentChart) - 1;
		if(chart >= _numCharts)
			return NULL;

		char *basePtr = reinterpret_cast<char *>(NULL) + 1 + chart;
		return basePtr;
	}

	void Work(void *ptr)
	{
		int chartNo = static_cast<unsigned int>(reinterpret_cast<char *>(ptr) - reinterpret_cast<char *>(NULL) - 1);

		CollectChart(static_cast<int>(_firstChart) + chartNo);
		ReportProgress();
	}

	void Twiddle()
	{
		// Pump the session so it doesn't time out
		if(_session)
			_session->ProcessData();

		const char *reportString = "0...1...2...3...4...5...6...7...8...9...\n ";

		unsigned int newProg = static_cast<unsigned int>(static_cast<__int64>(_chartsFinished.value) * static_cast<__int64>(41) / static_cast<__int64>(_numCharts));

		while(_reportedProgress < newProg)
			printf("%c", reportString[_reportedProgress++]);
	}
};

static void CreateChartsLocal(const CString &basePath, const LinkedList<rpLight> *lights)
{
	rpCollectChartJob2 job(0, geometry->numCharts, NULL, false);

	mlDispatcher->InstallJob(&job, true);
}

static void CreateChartsDistributed(const CString &basePath, const LinkedList<rpLight> *lights)
{
	Vector<bool> chartsLocallyCalculated(TDP::Memory::DefaultAllocator());
	chartsLocallyCalculated.Resize(NULL, geometry->numCharts, false);

	DistributionSession *session = config.distributionSession;
	while(session->LoggedIn() && session->ConnectionActive())
	{
	restartWorkUnit:
		Sleep(10);
		session->ProcessData();

		UInt32 firstUnit, numUnits;
		if(!session->RequestWork(config.distributionJobKey, firstUnit, numUnits))
		{
			if(!session->ConnectionActive())
			{
				printf("Lost connection!\n");
				break;	// Lost the connection
			}

			if(config.distributionSlave)
				return;		// Assume this is finished

			// Not a slave process, wait until the job is actually done
			while(session->ConnectionActive())
			{
				int status;
				if(session->RequestJobStatus(config.distributionJobKey, status))
				{
					if(status == DistributionSession::JS_Finished)
					{
						printf("Synchronizing external work units...\n");
						// Synchronize all output units that we don't have
						for(unsigned int i=0;i<geometry->numCharts;i++)
						{
							if(!chartsLocallyCalculated[i])
							{
								char fname[4096];
								sprintf(fname, "%s_photons/chart%i.primarylights", basePath.Chars(), i);
								session->SynchronizeFile(fname, true, true);
								sprintf(fname, "%s_photons/chart%i.radpoints", basePath.Chars(), i);
								session->SynchronizeFile(fname, true, true);
								sprintf(fname, "%s_photons/chart%i.directlight", basePath.Chars(), i);
								session->SynchronizeFile(fname, true, true);
								sprintf(fname, "%s_photons/chart%i.chartdata", basePath.Chars(), i);
								session->SynchronizeFile(fname, true, true);
							}
						}

						if(!session->ConnectionActive())
							goto abortDistribution;
						return;
					}
					if(status == DistributionSession::JS_Outstanding)
						goto restartWorkUnit;	// Work became available again, a worker might have died
				}
				Sleep(1000);
			}

			// Fell through the loop, this means the connection is dead
			goto abortDistribution;
		}

		printf("Granule %i + %i: Syncing inputs... ", firstUnit, numUnits);

		// Synchronize inputs
		for(unsigned int i=0;i<numUnits;i++)
		{
			char fname[4096];
			sprintf(fname, "%s_photons/chart%i.radsamples", basePath.Chars(), firstUnit + i);
			session->SynchronizeFile(fname, !config.distributionSlave, config.distributionSlave);
		}

		printf("Working... ");

		rpCollectChartJob2 job(firstUnit, numUnits, session, true);
		mlDispatcher->InstallJob(&job, true);

		printf("Syncing outputs... ");

		// Synchronize outputs
		for(unsigned int i=0;i<numUnits;i++)
		{
			char fname[4096];
			sprintf(fname, "%s_photons/chart%i.primarylights", basePath.Chars(), firstUnit + i);
			session->SynchronizeFile(fname, true, true);
			sprintf(fname, "%s_photons/chart%i.radpoints", basePath.Chars(), firstUnit + i);
			session->SynchronizeFile(fname, true, true);
			sprintf(fname, "%s_photons/chart%i.directlight", basePath.Chars(), firstUnit + i);
			session->SynchronizeFile(fname, true, true);
			sprintf(fname, "%s_photons/chart%i.chartdata", basePath.Chars(), firstUnit + i);
			session->SynchronizeFile(fname, true, true);

			chartsLocallyCalculated[firstUnit + i] = true;
		}

		printf("Done\n");
	}

abortDistribution:
	fprintf(stderr, "Lost connection to the distribution server!\n");
	if(!config.distributionSlave)
	{
		fprintf(stderr, "... Rerunning everything locally instead\n");
		chartsLocallyCalculated.Release();
		CreateChartsLocal(basePath, lights);
	}
}

void CreateCharts(const CString &basePath, const LinkedList<rpLight> *lights)
{
	chartBasePath.Set(NULL, basePath);
	chartLights = lights;

	if(config.distribute)
		CreateChartsDistributed(basePath, lights);
	else
		CreateChartsLocal(basePath, lights);
}
